import numpy as np
import time
import math


def cal_precision(t, r,k):
    """
    The numerator of Precision
    t represent the true list for a user
    r represent the recommendation list for a user
    """

    # if isinstance(t, list):
    #     # If t is already a list, return it wrapped in another list
    #     t= [t]
    # else:
    #     # If t is not a list, create a new list with t as the only element and return it
    #     t= [[t]]
    # if isinstance(r, list):
    #     # If t is already a list, return it wrapped in another list
    #     r = [r]
    sum_num=0

    for i in range(len(t)):
        sum_num += len(list(set(t[i]).intersection(set(r[i][0:k]))))
    pre = sum_num/(len(t)*k)
    return pre




def cal_recall(t, r,k):
    """
    The numerator of Precision
    t represent the true list for a user
    r represent the recommendation list for a user
    """
    # if isinstance(t, list):
    #     # If t is already a list, return it wrapped in another list
    #     t = [t]
    # else:
    #     # If t is not a list, create a new list with t as the only element and return it
    #     t = [[t]]
    # if isinstance(r, list):
    #     # If t is already a list, return it wrapped in another list
    #     r = [r]
    sum_recall = 0.0
    num_users = len(r)
    true_users = 0
    for i in range(num_users):
        r_list = list(r[i])
        act_set = set(t[i])
        pred_set = set(r_list[:k])
        if len(act_set) != 0:
            sum_recall += len(act_set & pred_set) / float(len(act_set))
            true_users += 1
    return sum_recall / true_users


def cal_hit(t, r,k):
    """
    calc the hit for a user
    t represent the true list for a user
    r represent the recommendation list for a user
    """
    # if isinstance(t, list):
    #     # If t is already a list, return it wrapped in another list
    #     t = [t]
    # else:
    #     # If t is not a list, create a new list with t as the only element and return it
    #     t = [[t]]
    # if isinstance(r, list):
    #     # If t is already a list, return it wrapped in another list
    #     r = [r]
    sum_num = 0
    all_true_num = 0
    for i in range(len(t)):
        h_temp = len(list(set(t[i]).intersection(set(r[i][0:k]))))
        if h_temp != 0:
            sum_num += 1

    hit = sum_num / len(t)
    # print("pre_time hit", time.time() - begin_time)
    return hit


def cal_mrr(t, r,k):
    # if isinstance(t, list):
    #     # If t is already a list, return it wrapped in another list
    #     t = [t]
    # else:
    #     # If t is not a list, create a new list with t as the only element and return it
    #     t = [[t]]
    # if isinstance(r, list):
    #     # If t is already a list, return it wrapped in another list
    #     r = [r]
    mrr_temp = 0
    for i in range(len(t)):
        rec_one = list(r[i])
        rec_r = list(set(r[i][0:k]).intersection(set(t[i])))
        if len(rec_r) != 0:
            mrr_temp += 1 / (rec_one.index(rec_r[0]) + 1)
    mrr = mrr_temp / len(t)
    # print("pre_time cal_mrr", time.time() - begin_time)
    return mrr

def idcg_k(k):
    res = sum([1.0 / math.log(i + 2, 2) for i in range(k)])
    if not res:
        return 1.0
    else:
        return res

def ndcg_k(actual, predicted, topk):
    # if isinstance(actual, list):
    #     # If t is already a list, return it wrapped in another list
    #     actual = [actual]
    # else:
    #     # If t is not a list, create a new list with t as the only element and return it
    #     actual = [[actual]]
    # if isinstance(predicted, list):
    #     # If t is already a list, return it wrapped in another list
    #     predicted = [predicted]

    res = 0
    for user_id in range(len(actual)):
        k = min(topk, len(actual[user_id]))
        idcg = idcg_k(k)
        dcg_k = sum([int(predicted[user_id][j] in
                         set(actual[user_id])) / math.log(j + 2, 2) for j in range(topk)])
        res += dcg_k / idcg
    return res / float(len(actual))


def evaluate(true_list, rec_list, k):
    return cal_precision(true_list, rec_list, k), cal_recall(true_list, rec_list, k), cal_hit(true_list, rec_list, k), \
           cal_mrr(true_list, rec_list, k), ndcg_k(true_list, rec_list, k)

def cal_result(true_list, rec_list):
    topK = [1, 5, 10, 20]  # 这里需要加一个20
    # topK = [1, 5, 10]
    # ACC@1，ACC@5,ACC@10,ACC@20
    ACC_now = {}
    for K in topK:
        ACC_now[K] = 0
    # Recall@1，Recall@5,Recall@10,Recall@20
    Recall_now = {}
    for K in topK:
        Recall_now[K] = 0

    # HR@1，HR@5,HR@10,HR@20
    HR_now = {}
    for K in topK:
        HR_now[K] = 0
    # MRR@1，MRR@5,MRR@10,MRR@20
    MRR_now = {}
    for K in topK:
        MRR_now[K] = 0
    # NDCG@1，NDCG@5,NDCG@10,NDCG@20
    NDCG_now = {}
    for K in topK:
        NDCG_now[K] = 0
    for k in topK:
        pre, recall, hr, mrr, ndcg = evaluate(true_list, rec_list, k)
        ACC_now[k]+=pre
        Recall_now[k]+=recall
        HR_now[k]+=hr
        MRR_now[k]+=mrr
        NDCG_now[k]+=ndcg
    return ACC_now,Recall_now,HR_now,MRR_now,NDCG_now